package com.gitee.wsl.net

import com.gitee.wsl.time.YearMonth
import io.ktor.http.HttpStatusCode
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.encoding.encodeStructure
import kotlin.time.ExperimentalTime

inline val HttpStatusCode.Companion.EarlyHints get() = HttpStatusCode(103, "Early Hints")
inline val HttpStatusCode.Companion.AlreadyReported get() = HttpStatusCode(208, "Already Reported")
inline val HttpStatusCode.Companion.IMUsed get() = HttpStatusCode(226, "IM Used")

inline val HttpStatusCode.Companion.ContentTooLarge get() = HttpStatusCode(413, "Content Too Large")
inline val HttpStatusCode.Companion.URITooLong get() = HttpStatusCode(414, "URI Too Long")

inline val HttpStatusCode.Companion.RangeNotSatisfiable get() = HttpStatusCode(416, "Requested Range Not Satisfiable")

inline val HttpStatusCode.Companion.ImATeapot get() = HttpStatusCode(418, "I'm a teapot")
inline val HttpStatusCode.Companion.MisdirectedRequest get() = HttpStatusCode(421, "Misdirected Request")

inline val HttpStatusCode.Companion.PreconditionRequired get() = HttpStatusCode(428, "Precondition Required")

inline val HttpStatusCode.Companion.RequestHeaderFieldsTooLarge get() = HttpStatusCode(431, "Request Header Fields Too Large")
inline val HttpStatusCode.Companion.UnavailableForLegalReasons get() = HttpStatusCode(451, "Unavailable For Legal Reasons")
inline val HttpStatusCode.Companion.HttpVersionNotSupported get() = HttpStatusCode(505, "HTTP Version Not Supported")

inline val HttpStatusCode.Companion.LoopDetected get() = HttpStatusCode(508, "Loop Detected")
inline val HttpStatusCode.Companion.NotExtended get() = HttpStatusCode(510, "Not Extended")
inline val HttpStatusCode.Companion.NetworkAuthenticationRequired get() = HttpStatusCode(511, "Network Authentication Required")

private val extStatusCodes = listOf(
    HttpStatusCode.EarlyHints,
    HttpStatusCode.AlreadyReported,
    HttpStatusCode.IMUsed,
    HttpStatusCode.ContentTooLarge,
    HttpStatusCode.URITooLong,
    HttpStatusCode.RangeNotSatisfiable,
    HttpStatusCode.ImATeapot,
    HttpStatusCode.MisdirectedRequest,
    HttpStatusCode.PreconditionRequired,
    HttpStatusCode.RequestHeaderFieldsTooLarge,
    HttpStatusCode.UnavailableForLegalReasons,
    HttpStatusCode.HttpVersionNotSupported,
    HttpStatusCode.LoopDetected,
    HttpStatusCode.NotExtended,
    HttpStatusCode.NetworkAuthenticationRequired,
)

val HttpStatusCode.Companion.allStatusCodesExt get() = allStatusCodes + extStatusCodes

fun HttpStatusCode.Companion.of(status: Int): HttpStatusCode =
    extStatusCodes.firstOrNull{ it.value == status } ?: HttpStatusCode.fromValue( status)

fun HttpStatusCode.isInformational(): Boolean = value in (100 until 200)

fun HttpStatusCode.isRedirect(): Boolean = value in (300 until 400)

fun HttpStatusCode.isClientError(): Boolean = value in (400 until 500)

fun HttpStatusCode.isServerError(): Boolean = value in (500 until 600)

typealias HttpStatusCodeP = @Serializable(with = HttpStatusCodeSerializer::class) HttpStatusCode

/**
 * A serializer for [YearMonth] that represents a value as its components.
 *
 * JSON example: `{"year":2020,"month":12}`
 */
@OptIn(ExperimentalTime::class)
object HttpStatusCodeSerializer : KSerializer<HttpStatusCode> {
    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("net.gitee.wsl.net.HttpStatusCode") {
            element<Int>("year")
            element<Short>("month")
        }

    @OptIn(ExperimentalSerializationApi::class)
    override fun deserialize(decoder: Decoder): HttpStatusCode =
        decoder.decodeStructure(descriptor) {
            var code: Int? = null
            var description: String? = null
            loop@ while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> code = decodeIntElement(descriptor, 0)
                    1 -> description = decodeStringElement(descriptor, 1)
                    CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
                    else -> throw SerializationException("Unexpected index: $index")
                }
            }
            if (code == null) throw MissingFieldException(missingField = "code", serialName = descriptor.serialName)
            if (description == null) throw MissingFieldException(missingField = "description", serialName = descriptor.serialName)
            HttpStatusCode(code, description)
        }

    override fun serialize(encoder: Encoder, value: HttpStatusCode) {
        encoder.encodeStructure(descriptor) {
            encodeIntElement(descriptor, 0, value.value)
            encodeStringElement(descriptor, 1, value.description)
        }
    }
}